# gozerbot/plugins.py
#
#

""" provide plugin infrastructure """

__copyright__ = 'this file is in the public domain'

## gozerbot imports

from gozerbot.stats import stats
from gozerbot.tests import tests
from gozerbot.datadir import datadir
from users import users
from irc.monitor import outmonitor, saymonitor
from xmpp.monitor import xmppmonitor
from utils.log import rlog
from utils.exception import handle_exception
from utils.generic import checkchan
from utils.locking import lockdec, funclocked, Locked
from utils.generic import plugnames, waitforqueue, uniqlist, makeoptions, makeargrest, cleanpycfile
from gozerimport import gozer_import, force_import
from persist.persist import Persist
from persist.persistconfig import PersistConfig
from config import config
from commands import cmnds
from callbacks import callbacks, jcallbacks, gn_callbacks
from redispatcher import rebefore, reafter
from aliases import aliascheck, aliasget
from ignore import shouldignore
from threads.thr import start_new_thread, getname
from persist.persiststate import PersistState
from simplejson import loads
from morphs import inputmorphs, outputmorphs
from eventbase import EventBase
from admin import cmndtable, pluginlist

# basic imports
import os, os.path, thread, time, Queue, re, copy

## END IMPORT

## LOCK SECTION

loadlock = thread.allocate_lock()
loadlocked = lockdec(loadlock)

## END LOCK

class Plugins(object):

    """
        hold all the plugins.

    """

    def __init__(self):
        self.plugs = {} # dict with the plugins
        self.reloadlock = thread.allocate_lock()
        # persisted data for deny of plugins (blacklist)
        self.plugdeny = Persist(datadir + os.sep + 'plugdeny', init=False)
        if not self.plugdeny.data:
            self.plugdeny.data = []
        # persisted data for allowing of plugins (whitelist)
        self.plugallow = Persist(datadir + os.sep + 'plugallow', init=False)
        if not self.plugallow.data:
            self.plugallow.data = []
        self.avail = [] # available plugins
        self.ondisk = [] # plugisn available for reload
        self.initcalled = [] # plugins which init functions are called
        self.overloads = {} # plugins to overload
        self.activated = {}
        for plug in config['plugdeny']:
            self.disable(plug)

    def __getitem__(self, item):

        """
            return plugin.

        """

        if self.plugs.has_key(item):
            return self.plugs[item]
        else:
            return None

    def get(self, item, attr):

        """
            get attribute of plugin.

            :param item: plugin to get attribute of
            :type item: string
            :param attr: attribute to fetch
            :type attr: string

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.get

        """

        if self.plugs.has_key(item):
            return getattr(self.plugs[item], attr)

    def whatperms(self):

        """
            return what permissions are possible.

            :rtype: list

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.whatperms

        """

        result = []

        # search RE callbacks before the commands
        for i in rebefore.whatperms():
            if not i in result:
                result.append(i)

        # search the commands
        for i in cmnds.whatperms():
            if not i in result:
                result.append(i)

        # search RE callbacks after commands
        for i in reafter.whatperms():
            if not i in result:
                result.append(i)

        result.sort()
        return result

    def exist(self, name):

        """
            see if plugin exists.

            :param name: name of plugin to check for
            :type name: string
            :rtype: boolean

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.exist

        """

        if self.plugs.has_key(name):
            return True
        return False

    def disable(self, name):

        """
            prevent plugin to be loaded. plugins does get imported but 
            commands, callbacks, monitors etc are not enabled.

            :param name: name of the plugin to disable
            :type name: string

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.disable

        """

        try:
            config['loadlist'].remove(name)
            config.save()
            self.plugdeny.data.append(name)
            self.plugdeny.save()
            
        except:
            pass

    def enable(self, name):

        """
            enable plugin.

            :param name: name of plugin to enable
            :type name: string

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.enable

        """

        try:

            if name not in config['loadlist']:
                config['loadlist'].append(name)
                config.save()

        except KeyError:
            pass

        try:
            self.plugdeny.data.remove(name)
            self.plugdeny.save()

        except ValueError:
            pass

    def plugsizes(self):

        """
            call the size() function in all plugins.

            :rtype: list .. list of plugin sizes

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.plugsizes
        """

        reslist = []
        cp = dict(self.plugs)
        for i, j in cp.iteritems():
            try:
                reslist.append("%s: %s" % (i, j.size()))
            except AttributeError:
                pass
        return reslist

    def list(self):

        """
            list of registered plugins.

            :rtype: list .. list of enabled plugins

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.list

        """

        self.avail.sort()
        return self.avail

    def plugimport(self, mod, name):

        """
            import a plugin.

            :param mod: module to import plugin from
            :type mod: string
            :param name: name of the plugin to import
            :type name: string
            :rtype: module .. the plugin

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.plugimport

        """
        
        if name in config['loadlist']:
            return self.load(mod, name)

    def regplugin(self, mod, name):

        """
            register plugin.

            :param mod: module to import plugin from
            :type mod: string
            :param name: name of the plugin to import
            :type name: string
            :rtype: module .. the plugin

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.regplugin

        """

        name = name.lower()
        mod = mod.lower()
        modname = mod + '.' + name

        # see if plugin is in deny
        if name in self.avail:
            rlog(0, 'plugins', '%s already registered' % name)
            return

        if name in config['plugdeny']:
            rlog(0, 'plugins', '%s is in config.plugdeny .. not loading' % name)
            return

        if name in self.plugdeny.data:
            rlog(0, 'plugins', '%s.%s in deny .. not loading' % (mod, name))
            return 0

        if config.has_key('loadlist') and name not in config['loadlist'] and 'gplugs' in modname and name not in self.plugallow.data:
                rlog(9, 'plugins', 'not loading %s.%s' % (mod, name))
                return 0

        # if plugin is already registered unload it
        if self.plugs.has_key(name):
            rlog(10, 'plugins', 'overloading %s plugin with %s version' % (name, mod))
            self.unloadnosave(name)

        # create the plugin data dir
        if hasattr(os, 'mkdir'):
            if not os.path.isdir(datadir + os.sep + 'plugs'):
                os.mkdir(datadir + os.sep + 'plugs')

            if not os.path.isdir(datadir + os.sep + 'plugs' + os.sep + name):
                os.mkdir(datadir + os.sep + 'plugs' + os.sep + name)

        # import the plugin
        plug = self.plugimport(mod, name)

        if plug:
            rlog(0, 'plugins', "%s.%s registered" % (mod, name))

            if name not in self.avail:
                self.avail.append(name)

            return plug

        else:
            rlog(10, 'plugins', "can't import %s.%s .. try plug-enable" % (mod, name))
         
    def showregistered(self):

        """
            show registered plugins.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.showregistered

        """

        self.avail.sort()
        rlog(10, 'plugins', 'registered %s' % ' .. '.join(self.avail))
        self.overload()

    def regdir(self, dirname, exclude=[]):

        """
            register a directory.

            :param dirname: directory to import plugins from
            :type dirname: string
            :param exclude: plugins to exclude from importing
            :type exclude: list .. list of plugin names
            :rtype: list .. list of plugin names that are registered

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.regdir

        """

        threads = []
        plugs = []
        for plug in plugnames(dirname):
            if plug in exclude or plug.startswith('.'):
                continue
            try:
                self.regplugin(dirname, plug)
                plugs.append(plug)
            except:
                handle_exception()
        self.ondisk.extend(plugs)
        return plugs

    def regcore(self): 

        """
            register core plugins.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.regcore

        """

        self.plugdeny.init([])
        self.plugallow.init([])
        avail = [] 
        plugs = force_import('gozerbot.plugs')

        for i in plugs.__plugs__:

            if i not in avail:

                try:
                    self.regplugin('gozerbot.plugs', i)
                except Exception, ex:
                    handle_exception()
                else:
                    avail.append(i)

        self.ondisk.extend(avail)

    def enableall(self):

        """
            enable all plugins

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.enableall

        """

        for name in self.available():
            self.enable(name)

    def regplugins(self):

        """
            register all plugins.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.regplugins

        """

        self.regcore()
        avail = []

        # check for myplugs directory
        if os.path.isdir('myplugs'):
            avail.extend(self.regdir('myplugs'))  

            for i in os.listdir('myplugs'):

                if i.startswith('.'):
                    continue

                if os.path.isdir('myplugs' + os.sep + i):
                    avail.extend(self.regdir('myplugs' + os.sep + i))  
        else:
            rlog(10, 'plugins', 'no myplugs directory found')

        # check for gplugs package
        try:
            gplugs = gozer_import('gplugs')
        except ImportError:
            rlog(20, 'plugins', "no gplugs package found")
            gplugs = None

        if gplugs:

            for i in gplugs.__plugs__:

                try:
                    self.regplugin('gplugs', i)
                    avail.append(i)
                except Exception, ex:
                    handle_exception()

        if config.get('db_driver') == "olddb":
            # check for gplugs package
            try:
                gplugs = gozer_import('gplugs.olddb')
            except ImportError:
                rlog(20, 'plugins', "no gplugs.old package found")
                gplugs = None

            if gplugs:

                for i in gplugs.__plugs__:

                    try:
                        self.regplugin('gplugs.olddb', i)
                        avail.append(i)
                    except Exception, ex:
                        handle_exception()
        else:
            # check for gplugs package
            try:
                gplugs = gozer_import('gplugs.alchemy')
            except ImportError:
                rlog(20, 'plugins', "no gplugs.alchemy package found")
                gplugs = None

            if gplugs:

                for i in gplugs.__plugs__:

                    try:
                        self.regplugin('gplugs.alchemy', i)
                        avail.append(i)
                    except Exception, ex:
                        handle_exception()

        self.ondisk.extend(avail)
        self.readoverload()
        start_new_thread(self.showregistered, ())

    def readoverload(self):

        """ 
            see if there is a permoverload file and if so use it to overload
            permissions based on function name.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.readoverload

        """

        try:
            overloadfile = open(datadir + os.sep + 'permoverload', 'r')
        except IOError:
            return

        try:

            for i in overloadfile:
                i = i.strip()
                splitted = i.split(',')

                try:
                    funcname = splitted[0].strip()
                    perms = []
                    for j in splitted[1:]:
                        perms.append(j.strip())
                except IndexError:
                    rlog(10, 'plugins', "permoverload: can't set perms of %s" \
% i)
                    continue

                if not funcname:
                    rlog(10, 'plugins', "permoverload: no function provided")
                    continue

                if not perms:
                    rlog(10, 'plugins', "permoverload: no permissions \
provided for %s" % funcname)
                    continue

                self.overloads[funcname] = perms

        except Exception, ex:
             handle_exception()

    def overload(self):

        """
            overload functions in self.overloads. 

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.overload

        """
        for funcname, perms in self.overloads.iteritems():

            if self.permoverload(funcname, perms):
                rlog(0, 'plugins', '%s permission set to %s' % (funcname, \
perms))

    def available(self):

        """
            available plugins not yet registered.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.available

        """

        self.ondisk.sort()
        return self.ondisk

    def saveplug(self, plugname):

        """
            call save() function of plugin.

            :param plugname: name of the plugin to call save() on
            :type plugname: string

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.saveplug
        """

        try:
            self.plugs[plugname].save()

        except AttributeError:
            pass

        except KeyError:
            pass

    def save(self):

        """
            call registered plugins save.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.save
 
        """

        for plug in self.plugs.values():

            try:
                plug.save()

            except AttributeError:
                pass

            except Exception, ex:
                handle_exception()

    def save_cfg(self):

        """
            call registered plugins configuration save.

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.save_cfg

        """

        for plug in self.plugs.values():
            try:
                cfg = getattr(plug, 'cfg')
                if isinstance(cfg, PersistConfig):
                    try:
                        cfg.save()
                    except:
                        handle_exception()
            except AttributeError:
                continue

    def save_cfgname(self, name):

        """
            save plugin persisted config data.

            :param name: name of the plugin to call cfg.save for
            :type name: string

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.save_cfgname
        """

        try:
            plug = self.plugs[name]
            cfg = getattr(plug, 'cfg')

            if isinstance(cfg, PersistConfig):

                try:
                    cfg.save()
                except:
                    handle_exception()

        except (AttributeError, KeyError):
            pass
	
    def exit(self):

        """
            call shutdown on all registered plugins. 

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.exit

        """

        self.save()
        threadlist = []

        # call shutdown on all plugins
        for name, plug in self.plugs.iteritems():

            try:
                shutdown = getattr(plug, 'shutdown')
                thread = start_new_thread(shutdown, ())
                threadlist.append((name, thread))

                try:
                    self.initcalled.remove(name)
                except ValueError:
                    pass

            except AttributeError:
                continue

            except Exception, ex:
                rlog(10, 'plugins', 'error shutting down %s: %s' % (name, str(ex)))

        # join shutdown threads
        try:

            for name, thread in threadlist:
                thread.join()
                rlog(10, 'plugins', '%s shutdown finished' % name)
        except:
            handle_exception()

    def getoptions(self, command):

        """
            return options entry of a command.

            :param command: command name to get options of
            :type command: string
            :rtype: dict

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.getoptions

        """

        return cmnds.getoptions(command)

    def getdepend(self, plugname):

        """
            get plugins the plugin depends on.  NOT USED ANYMORE ..

            we use the __depending__ attribute now which checks for the 
            reverse case ala what plugins depends on this plugin. code is 
            in reload().

        """

        # try to import the plugin
        if plugname in self.plugs:
            plug = self.plugs[plugname]
        else:
            for mod in ['gozerbot.plugs', 'gplugs', 'myplugs']:
                try:
                    plug = gozer_import('%s.%s' % (mod, plugname))
                except ImportError:
                    continue

        # check for the __depend__ attribute           
        try: 
            depends = plug.__depend__
        except:
            depends = []

        return depends

    def load(self, mod , name, enable=True):
        #if name in config['plugdeny']:
        #     return
        # force an import of the plugin
        modname = mod + '.' + name
        self.down(name)
        self.unload(name)
        if enable:
            self.enable(name)
        plug = self.plugs[name] = gozer_import(modname)
        plug.loadtime = time.time()
        if enable:
            self.enable(name)
            self.overload()
            # call plugins init() function
            try:
                rlog(0, 'plugins', 'calling %s init()' % modname)
                plug.init()
                self.initcalled.append(modname)

            except (AttributeError, KeyError):
                pass

            except Exception, ex:
                rlog(10, 'plugins', '%s module init failed' % name)
                raise

            rlog(0, 'plugins', 'enabled %s' % name)

        self.activate(name)
        return plug

    #@loadlocked
    def reload(self, mod, name, enable=True):

        """
            reload plugin.

            :param mod: module to import plugin from
            :type mod: string
            :param name: name of the plugin to reload
            :type name: string
            :param enable: whether plugin should be enabled on reload
            :type enable: boolean  
            :rtype: list .. list of names of reloaded plugins

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.reload

        """

        # create the plugin data dir
        if not os.path.isdir(datadir + os.sep + 'plugs'):
            os.mkdir(datadir + os.sep + 'plugs')

        if not os.path.isdir(datadir + os.sep + 'plugs' + os.sep + name):
            os.mkdir(datadir + os.sep + 'plugs' + os.sep + name)

        reloaded = []
        modname = mod + '.' + name

        # force an import of the plugin
        plug = self.load(mod, name)

        # recurse the reload function if plugin is a dir
        try:
            for p in plug.__plugs__:
                self.load(modname, p)
                reloaded.append(p)

        except (KeyError, AttributeError):
            pass

        rlog(0, 'plugins', 'reloaded plugin %s' % modname)
        reloaded.append(name)
        self.plugallow.data.append(name)

        try:
            self.plugdeny.data.remove(name)
        except ValueError:
            pass

        if name not in self.avail:
            self.avail.append(name)

        # recurse on plugins the depend on this plugin
        try:
            depends = plug.__depending__

            for plug in depends:
                rlog(10, 'plugins', 'loading depending plugin %s (%s)' % (plug, name))
                self.load(mod, plug, False)
                reloaded.append(plug)

        except AttributeError:
            pass

        return reloaded

    def activate(self, plugname):
        self.activated[plugname] = True
        try:
            cmnds.activate(plugname)
            callbacks.activate(plugname)
            gn_callbacks.activate(plugname)
            jcallbacks.activate(plugname)
            rebefore.activate(plugname)
            reafter.activate(plugname)
            saymonitor.activate(plugname)
            outmonitor.activate(plugname)
            xmppmonitor.activate(plugname)
            tests.activate(plugname)
            outputmorphs.activate(plugname)
            inputmorphs.activate(plugname)
        except Exception, ex:
            handle_exception()
            return 0

    def down(self, plugname):
        self.activated[plugname] = False
        try:
            cmnds.disable(plugname)
            callbacks.disable(plugname)
            gn_callbacks.disable(plugname)
            jcallbacks.disable(plugname)
            rebefore.disable(plugname)
            reafter.disable(plugname)
            saymonitor.disable(plugname)
            outmonitor.disable(plugname)
            xmppmonitor.disable(plugname)
            tests.disable(plugname)
            outputmorphs.disable(plugname)
            inputmorphs.disable(plugname)
        except Exception, ex:
            handle_exception()
            return 0

    def unload(self, plugname):

        """
            unload plugin.

            :param plugname: name of the plugin to unload
            :type plugname: string
            :rtype: list .. list of unloaded plugins

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.unload
 
        """

        # call plugins shutdown function if available
        unloaded = [plugname, ]

        # recurse if plugin is dir
        try:
            plug = self.plugs[plugname]

            for p in plug.__plugs__:
                if p == plug:
                    raise Exception("same plugin name as dir name (%s)" % plugname)
                unloaded.extend(self.unload(p))

        except (KeyError, AttributeError):
            pass

        # save and unload
        for plugname in unloaded:
            self.saveplug(plugname)
            self.unloadnosave(plugname)

            try:
                self.avail.remove(plugname)

            except ValueError:
                pass

        return unloaded

    def unloadnosave(self, plugname):

        """
            unload plugin without saving.

            :param plugname: name of the plugin to unload
            :type plugname: string
            :rtype: list .. list of unloaded plugins

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.unloadnosave

        """

        # call shutdown function
        try:
            self.plugs[plugname].shutdown()
            rlog(10, 'plugins', '%s shutdown called' % plugname)

        except (AttributeError, KeyError):
            pass

        except Exception, ex:
            handle_exception()

        # remove from plugallow
        try:
            self.plugallow.data.remove(plugname)
        except (KeyError, ValueError):
            pass

        # remove from avail list
        try:
            self.avail.remove(plugname)
        except ValueError:
            pass

        # remove from initcalled list
        try:
            self.initcalled.remove(plugname)
        except ValueError:
            pass

        # unload commands, RE callbacks, callbacks, monitorsetc.
        try:
            cmnds.unload(plugname)
            callbacks.unload(plugname)
            gn_callbacks.unload(plugname)
            jcallbacks.unload(plugname)
            rebefore.unload(plugname)
            reafter.unload(plugname)
            saymonitor.unload(plugname)
            outmonitor.unload(plugname)
            xmppmonitor.unload(plugname)
            tests.unload(plugname)
            outputmorphs.unload(plugname)
            inputmorphs.unload(plugname)

            if self.plugs.has_key(plugname):
                del self.plugs[plugname]

        except Exception, ex:
            handle_exception()
            return 0

        rlog(0, 'plugins', '%s unloaded' % plugname)
        return 1

    def whereis(self, what):

        """
            locate command.

            :param what: name of command to search
            :type what: string
            :rtype: string .. plugin the command is in

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.whereis

        """

        return cmnds.whereis(what)

    def permoverload(self, funcname, perms):

        """
            overload permission of a function.

            :param funcname: name of function to overload permissions of
            :type funcname: string
            :param perms: permissions to overload
            :type perms: list .. list of permissions
            :rtype: boolean: whether overload worked or not

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.permoverload

        """

        if not rebefore.permoverload(funcname, perms):

            if not cmnds.permoverload(funcname, perms):

                if not reafter.permoverload(funcname, perms):
                    return False

        return True

    def woulddispatch(self, bot, ievent):

        """
            function to determine whether a event would dispatch.

            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :rtype: boolean .. whether the dispatch should fire

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.woulddispatch

        """
        #self.needreloadcheck(bot, ievent)
        (what, command) = self.dispatchtest(bot, ievent)

        #if what and not what.activate:
        #    return False

        if what and command:
            return True

        return False

    #@funclocked
    def dispatchtest(self, bot, ievent, direct=False):

        """
            return (dispatcher, command) on which command should fire.

            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :param direct: whether user permission should be checked
            :type direct: boolean .. when set user permission is NOT checked
            :rtype: tuple .. (dispatcher, command) tuples

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.dispatchtest
        """

        # check for ignore
        if shouldignore(ievent.userhost):
            return (None, None)

        # check for throttle
        if ievent.userhost in bot.throttle:
            return (None, None)

        # set target properly
        if ievent.txt.find(' | ') != -1:
            target = ievent.txt.split(' | ')[0]
        elif ievent.txt.find(' && ') != -1:
            target = ievent.txt.split(' && ')[0]
        else:
            target = ievent.txt

        result = []

        # first check for RE before commands dispatcher
        com = rebefore.getcallback(target)

        if com and not target.startswith('!'):
            com.re = True
            result = [rebefore, com]
        else:

            # try commands 
            if ievent.txt.startswith('!'):
                ievent.txt = ievent.txt[1:]

            aliascheck(ievent)
            com = cmnds.getcommand(ievent.txt)

            if com:
                com.re = False
                result = [cmnds, com]
                ievent.txt = ievent.txt.strip()

            else:

                # try RE after commands
                com = reafter.getcallback(target)
                if com:
                    com.re = True
                    result = [reafter, com]
        if result:

            # check for auto registration
            if config['auto_register'] and not users.getname(ievent.userhost):
                if bot.google:
                    users.add(ievent.userhost , [ievent.userhost, ], ['USER', ])
                elif not bot.jabber:
                    users.add("%s!%s" % (ievent.nick, ievent.userhost) , [ievent.userhost, ], ['USER', ])
                    bot.ratelimit(ievent.userhost, 20)
                else:
                    if ievent.groupchat:
                        users.add(ievent.userhost , [ievent.userhost, ], ['USER', ])
                        bot.ratelimit(ievent.userhost)
                    else:
                        users.add(ievent.stripped , [ievent.stripped, ], ['USER', ])
                        bot.ratelimit(ievent.stripped, 20)

            # check for anon access
            if config['anon_enable'] and not 'OPER' in result[1].perms:
                return result

            # check if command is allowed (all-add command)
            if com.name in bot.state['allowed'] or getname(com.func) in bot.state['allowed']:
                return result

            # check for channel permissions
            try:
                chanperms = bot.channels[ievent.channel.lower()]['perms']

                for i in result[1].perms:
                    if i in chanperms and not ievent.msg:
                        ievent.speed = 1
                        return result

            except (KeyError, TypeError):
                pass

            # if direct is set dont check the user database
            if direct:
                return result

            # use event.stripped in case of jabber 
            if bot.jabber and ievent.jabber:
                if not ievent.groupchat or ievent.jidchange:
                    if users.allowed(ievent.stripped, result[1].perms):
                        return result

            # irc users check
            if users.allowed(ievent.userhost, result[1].perms):
                return result

        return (None, None)

    def cmnd(self, bot, ievent, timeout=15, response=False, onlyqueues=True):

        """
            launch command and wait for result.

            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :param timeout: number of seconds to wait for a result
            :type timeout: integer
            :param response: whether to notify user we are running the command
            :type response: string
            :rtype: list .. list of results

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.cmnd

        """
        if response:
            ievent.reply('launching %s on %s bot' % (ievent.txt, bot.name))
        #ii = self.clonedevent(bot, ievent)
        #q = Queue.Queue()
        #ii.queues.append(q)
        #ii.onlyqueues = onlyqueues
        q = Queue.Queue()
        ievent.queues.append(q)
        self.trydispatch(bot, ievent)
        return waitforqueue(q, timeout)

    def waitdispatch(self, bot, ievent, direct=False):
        
        """
            dispatch command and wait for results.

            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :param direct: whether user permission should be checked
            :type direct: boolean .. when set user permission is NOT checked
            :rtype: boolean .. whether dispatch succeeded or not

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.waitdispatch

        """
        ievent.threaded = True
        return self.trydispatch(bot, ievent, direct, wait=True)

    def trydispatch(self, bot, ievent, direct=False, wait=False):

        """
            try to dispatch ievent.

            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :param direct: whether user permission should be checked
            :type direct: boolean .. when set user permission is NOT checked
            :param wait: whether function should wait for results
            :type wait: boolean
            :rtype: boolean .. whether dispatch succeeded or not

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.trydispatch

        """

        # test for ignore
        if shouldignore(ievent.userhost):
            return 0

        # set printto
        if ievent.msg:
            ievent.printto = ievent.nick
        else:
            ievent.printto = ievent.channel

        # see if ievent would dispatch
        # what is rebefore, cmnds of reafter, com is the command object
        # check if redispatcher or commands object needs to be used
        #self.needreloadcheck(bot, ievent)

        (what, com) = self.dispatchtest(bot, ievent, direct)

        if what:

            if com.allowqueue:
                ievent.txt = ievent.txt.replace(' || ', ' | ')

                if ievent.txt.find(' | ') != -1:

                    if ievent.txt[0] == '!':
                        ievent.txt = ievent.txt[1:]
                    else:
                        self.splitpipe(bot, ievent)
                        return

                elif ievent.txt.find(' && ') != -1:
                    self.multiple(bot, ievent)
                    return

            return self.dispatch(what, com, bot, ievent, wait)

    def dispatch(self, what, com, bot, ievent, wait=False):

        """
            do the actual dispatch of event.

            :param what: the dispatcher to dispatch the command on
            :type what: gozerbot.redispatcher.REdispatcher or gozerbot.commands.Commands
            :param com: the command to dispatch
            :type com: gozerbot.redispatcher.REcallback or gozerbot.commands.Command
            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :param wait: whether function should wait for results
            :type wait: boolean
            :rtype: boolean .. whether dispatch succeeded or not

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.dispatch

        """
        if bot.stopped:
            return False

        # make command options
        if com.options:
            makeoptions(ievent, com.options)
        else:
            makeoptions(ievent)

        # make arguments and rest
        makeargrest(ievent)
        ievent.usercmnd = True
        rlog(10, 'plugins', 'dispatching %s for %s' % (ievent.command, ievent.userhost))

        # call dispatch
        what.dispatch(com, bot, ievent, wait)
        return True

    def clonedevent(self, bot, event):

        """
            clone a event.

            
            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase
            :rtype: gozerbot.eventbase.EventBase

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.clonedevent

        """

        ie = copy.deepcopy(event)
        return ie


    def multiple(self, bot, ievent):

        """
            execute multiple commands.


            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.multiple

        """

        for i in ievent.txt.split(' && '):
            ie = self.clonedevent(bot, ievent)
            ie.txt = i
            #self.needreloadcheck(bot, ievent)
            self.trydispatch(bot, ie)

    def splitpipe(self, bot, ievent):

        """
            execute commands in a pipeline.

            :param bot: bot on which command is given
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event triggering the command
            :type ievent: gozerbot.eventbase.EventBase

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.splitpipe

        """

        origqueues = ievent.queues
        ievent.queues = []
        events = []
        txt = ievent.txt.replace(' || ', ' ##')

        # split commands
        for i in txt.split(' | '):
            item = i.replace(' ##', ' | ')
            ie = self.clonedevent(bot, ievent)
            ie.userhost = ievent.userhost
            ie.onlyqueues = True
            ie.txt = item.strip()
            #self.needreloadcheck(bot, ie)
            events.append(ie)

        # loop over events .. chain queues
        prevq = None

        for i in events[:-1]:
            q = Queue.Queue()
            i.queues.append(q)
            if prevq:
                i.inqueue = prevq
            prevq = q

        events[-1].inqueue = prevq
        events[-1].onlyqueues = False

        if origqueues:
            events[-1].queues = origqueues

        # check if all commands would dispatch
        for i in events:
            if not self.woulddispatch(bot, i):
                ievent.reply("can't execute %s" % str(i.txt))
                return

        # do the dispatch
        for i in events:
            (what, com) = self.dispatchtest(bot, i)
            if what:
                self.dispatch(what, com, bot, i)

    def needreloadcheck(self, bot, event, target=None):

        if cmndtable:

            try: 
                if target:
                    rlog(10, 'plugins', 'target set: %s' % target)
                    cmnd = 'target-set'
                    plugin = target
                else:
                    t = event.txt.split()[0]
                    cmnd = aliasget(t) or t
                    plugin = cmndtable[cmnd]

                rlog(10, 'plugins', 'cmnd: %s plugin: %s' % (cmnd, plugin))

                if self.exist(plugin):
                    return

                try:
                    self.reload('gozerbot.plugs', plugin)
                except ImportError, ex:

                    try:
                        self.reload('gplugs', plugin)
                    except ImportError, ex:
                        if config.get('db_driver') == 'olddb':
                            try:
                                self.reload('gplugs.olddb', plugin)
                            except ImportError, ex: pass
                        else:
                            try:
                                self.reload('gplugs.alchemy', plugin)
                            except ImportError, ex: pass
                        try:
                            self.reload('myplugs', plugin)
                        except ImportError, ex:
                            return

                rlog(100, 'plugins', 'reloaded %s' % plugin)

            except KeyError, ex:
                rlog(10, 'plugins', "can't find plugin to reload for %s" % event.txt.split()[0])


    def listreload(self, pluglist):

        """
            reload list of plugins.

            :param pluglist: list of plugin names
            :type pluglist: list
            :rtype: list .. list of plugins where reload failed

            .. literalinclude:: ../../gozerbot/plugins.py
                :pyobject: Plugins.listreload

        """

        failed = []

        # loop over the plugin list and reload them
        for what in pluglist:
            splitted = what[:-3].split(os.sep)
            mod = '.'.join(splitted[:-1])

            if not mod:
                if config.get('db_driver') == "olddb":
                    mod = "gplugs.olddb"
                elif config.get("db_driver") == "alchemy":
                    mod = "gplugs.alchemy"
                else:
                    mod = 'gplugs'

            plug  = splitted[-1]

            # reload the plugin
            try:
                self.reload(mod, plug)

            except Exception, ex:
                failed.append(what)

        return failed

## INIT SECTION

# THE plugins object
plugins = Plugins()

## END INIT

